using System.Reflection.Emit;
using Crux.Core.Data;

namespace Crux.Core.NHibernate;
/// <inheritdoc/>
public class UnitOfWork<TContext>(ISessionFactoryHelm<TContext> helm) : IDisposableObservable,
    IUnitOfWork<TContext>
    where TContext : NHibernateContext
{
    #region private fields
    /// <inheritdoc/>
    private readonly Lazy<TContext> context = new Lazy<TContext>(() =>
    {
        if (activator is null)
        {
            var sessionCtorType = new[] { typeof(ISession) };

            var dynamicMethod = new DynamicMethod("CreateInstance", typeof(TContext), sessionCtorType, true);

            var ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Nop);

            var ctor = typeof(TContext).GetConstructor(sessionCtorType);
            if (ctor is null)
                throw new MissingMemberException("UnableToFindSessionConstructor");

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Newobj, ctor);
            ilGenerator.Emit(OpCodes.Ret);

            activator = (DynamicContextActivator)dynamicMethod.CreateDelegate(typeof(DynamicContextActivator));
        }

        var session = helm.GetSession();

        return activator(session);
    });
    /// <inheritdoc/>
    public delegate TContext DynamicContextActivator(ISession session);
    /// <inheritdoc/>
    private static DynamicContextActivator? activator;
    #endregion  private fields

    #region implements IDisposableObservable
    /// <inheritdoc/>
    public bool IsDisposed { get; private set; }
    /// <inheritdoc/>
    public void Dispose()
    {
        if (IsDisposed) return;
        if (context.IsValueCreated)
        {
            using var _ = context.Value;
        }

        IsDisposed = true;
        GC.SuppressFinalize(this);
    }
    /// <inheritdoc/>
    public ValueTask DisposeAsync()
    {
        var completed = new ValueTask(Task.CompletedTask);

        if (IsDisposed)
        {
            return completed;
        }
        if (context.IsValueCreated)
        {
            using var _ = context.Value;
        }

        IsDisposed = true;
        GC.SuppressFinalize(this);

        return completed;
    }
    #endregion implements IDisposableObservable

    #region implements IUnitOfWork
    /// <inheritdoc/>
    public TContext Context => context.Value;

    /// <inheritdoc/>
    public TEntity Add<TEntity>(TEntity entity) where TEntity : class
    {
        Requires.NotNull(entity, nameof(entity));

        Context.Persist(entity);

        return entity;
    }

    /// <inheritdoc/>
    public async Task<TEntity> AddAsync<TEntity>(
        TEntity entity,
        CancellationToken cancellationToken = default) where TEntity : class
    {
        Requires.NotNull(entity, nameof(entity));

        await Context.PersistAsync(entity).ConfigureAwait(false);

        return entity;
    }

    /// <inheritdoc/>
    public IEnumerable<TEntity> Add<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        Requires.NotNull(entities, nameof(entities));
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            this.Context.Persist(entity);

            yield return entity;
        }
    }

    /// <inheritdoc/>
    public async Task<IEnumerable<TEntity>> AddAsync<TEntity>(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class
    {
        Requires.NotNull(entities, nameof(entities));
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            await Context.PersistAsync(entity).ConfigureAwait(false);
        }

        return entities;
    }

    /// <inheritdoc/>
    public T Update<T>(T entity) where T : class
    {
        Requires.NotNull(entity, nameof(entity));

        return Context.Merge(entity);
    }

    /// <inheritdoc/>
    public IEnumerable<T> UpdateRange<T>(IEnumerable<T> entities) where T : class
    {
        Requires.NotNull(entities, nameof(entities));
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            yield return Context.Merge(entity);
        }
    }

    /// <inheritdoc/>
    public int Update<T>(
        Expression<Func<T, bool>> where,
        Expression<Func<T, T>> updateExpression) where T : class
    {
        Requires.NotNull(where, nameof(where));
        Requires.NotNull(updateExpression, nameof(updateExpression));

        return Context.Query<T>().Where(where).Update(updateExpression);
    }

    /// <inheritdoc/>
    public async Task<T> UpdateAsync<T>(
        T entity,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(entity, nameof(entity));

        return await Context.MergeAsync(entity, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task<IEnumerable<T>> UpdateRangeAsync<T>(
        IEnumerable<T> entities,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(entities, nameof(entities));
        Requires.NotNullOrEmpty(entities, nameof(entities));

        var enumerables = new List<T>();

        foreach (var entity in entities)
        {
            var merged = await Context.MergeAsync(entity).ConfigureAwait(false);

            enumerables.Add(merged);
        }

        return enumerables;
    }

    /// <inheritdoc/>
    public async Task<int> UpdateAsync<T>(
        Expression<Func<T, bool>> where,
        Expression<Func<T, T>> updateExpression,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(where, nameof(where));
        Requires.NotNull(updateExpression, nameof(updateExpression));

        return await Context.Query<T>().Where(where).UpdateAsync(updateExpression, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public T Delete<T>(
        T entity) where T : class
    {
        Requires.NotNull(entity, nameof(entity));

        if (entity is ISoftDelete softDelete)
        {
            softDelete.Deleted = true;

            Context.Merge(softDelete);
        }
        else
        {
            Context.Delete(entity);
        }

        return entity;
    }

    /// <inheritdoc/>
    public IEnumerable<T> DeleteRange<T>(
        IEnumerable<T> entities) where T : class
    {
        Requires.NotNull(entities, nameof(entities));
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            if (entity is ISoftDelete softDelete)
            {
                softDelete.Deleted = true;

                Context.Merge(softDelete);
            }
            else
            {
                Context.Delete(entity);
            }

            yield return entity;
        }
    }

    /// <inheritdoc/>
    public int Delete<T>(
        Expression<Func<T, bool>> where) where T : class
    {
        Requires.NotNull(where, nameof(where));

        if (typeof(ISoftDelete).IsAssignableFrom(typeof(T)))
        {
            var entities = Context.Query<T>().Where(where);

            foreach (var entity in entities)
            {
                if (entity is ISoftDelete softDelete)
                {
                    softDelete.Deleted = true;

                    Context.Merge(softDelete);
                }
            }

            return entities.Count();
        }

        return Context.Query<T>().Where(where).Delete();
    }

    /// <inheritdoc/>
    public async Task<T> DeleteAsync<T>(
        T entity,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(entity, nameof(entity));

        if (entity is ISoftDelete softDelete)
        {
            softDelete.Deleted = true;

            await Context.MergeAsync(softDelete, cancellationToken).ConfigureAwait(false);
        }
        else
        {
            await Context.DeleteAsync(entity, cancellationToken).ConfigureAwait(false);
        }

        return entity;
    }

    /// <inheritdoc/>
    public async Task<IEnumerable<T>> DeleteRangeAsync<T>(
        IEnumerable<T> entities,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(entities, nameof(entities));
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            if (entity is ISoftDelete softDelete)
            {
                softDelete.Deleted = true;

                await Context.MergeAsync(softDelete, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                await Context.DeleteAsync(entity, cancellationToken).ConfigureAwait(false);
            }

        }

        return entities;
    }

    /// <inheritdoc/>
    public async Task<int> DeleteAsync<T>(
        Expression<Func<T, bool>> where,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(where, nameof(where));

        if (typeof(ISoftDelete).IsAssignableFrom(typeof(T)))
        {
            var entities = Context.Query<T>().Where(where);

            foreach (var entity in entities)
            {
                if (entity is ISoftDelete softDelete)
                {
                    softDelete.Deleted = true;

                    await Context.MergeAsync(softDelete, cancellationToken).ConfigureAwait(false);
                }
            }

            return entities.Count();
        }

        return await Context.Query<T>().Where(where).DeleteAsync(cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public int SoftDelete<T>(Expression<Func<T, bool>> where) where T : class, ISoftDelete
    {
        Requires.NotNull(where, nameof(where));

        return Context.Query<T>().Where(where).Update(c => c.Deleted == true);
    }

    /// <inheritdoc/>
    public async Task<int> SoftDeleteAsync<T>(
        Expression<Func<T, bool>> where,
        CancellationToken cancellationToken) where T : class, ISoftDelete
    {
        Requires.NotNull(where, nameof(where));

        return await Context.Query<T>().Where(where).UpdateAsync(c => c.Deleted == true, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public int PhysicsDelete<T>(Expression<Func<T, bool>> where) where T : class, ISoftDelete
    {
        Requires.NotNull(where, nameof(where));

        return Context.Query<T>().Where(where).Delete();
    }

    /// <inheritdoc/>
    public async Task<int> PhysicsDeleteAsync<T>(
        Expression<Func<T, bool>> where,
        CancellationToken cancellationToken = default) where T : class, ISoftDelete
    {
        Requires.NotNull(where, nameof(where));

        return await Context.Query<T>().Where(where).DeleteAsync(cancellationToken).ConfigureAwait(false);
    }

    #endregion implements IUnitOfWork
}